// 这个文件主要用来封装axios的配置（实例化请求配置、请求拦截器、相应拦截器）及相应的方法（登录跳转、消息提示、错误处理等）
// index.ts
import axios, { AxiosRequestConfig, Method } from "axios";
import router from "@/router";
import {message} from "ant-design-vue";
import { StorageSession } from "@/storage/storage";


/**
 * 请求失败后的错误统一处理
 * @param {Number} status 请求失败的状态码
 */
const errorHandle = (status: number, other: string) => {

    // 状态码判断
    switch (status) {
        case 302:
            message.error('接口重定向了，请联系管理员！');
            break;
        case 400:
            message.error("发出的请求有错误，服务器没有进行新建或修改数据的操作！")
            break;
        // 401: 未登录
        // 未登录则跳转登录页面，并携带当前页面的路径
        // 在登录成功后返回当前页面，这一步需要在登录页操作。
        case 401: //重定向
            message.error("登录失效！" )
            router.replace({
                path: '/',
            });
            break;
        // 403 token过期
        // 清除token并跳转登录页
        case 403:
            message.error("登录过期,用户得到授权，但是访问是被禁止的！" + status)
            // store.commit('token', null);
            setTimeout(() => {
                router.replace({
                    path: '/',
                });
            }, 1000);
            break;
        case 404:
            message.error("网络请求不存在==>" + status)
            break;
        case 406:
            message.error("请求的格式不可得==>" + status)
            break;
        case 408:
            message.error(" 请求超时！")
            break;
        case 410:
            message.error("请求的资源被永久删除，且不会再得到的==>" + status)
            break;
        case 422:
            message.error("当创建一个对象时，发生一个验证错误==>" + status)
            break;
        case 500:
            message.error("服务器发生错误，请检查服务器==>" + status)
            break;
        case 501:
            message.error("服务器发生错误，可能未实现==>" + status)
            break;
        case 502:
            message.error("网关错误==>" + status)
            break;
        case 503:
            message.error("服务不可用，服务器暂时过载或维护==>" + status)
            break;
        case 504:
            message.error("网关超时==>" + status)
            break;
        case 505:
            message.error("HTTP版本不受支持==>" + status)
            break
        default:
            message.error("其他错误错误==>" + status)
            break;
    }
}

// 定义接口
interface PendingType {
    url?: string;
    method?: Method;
    params: any;
    data: any;
    cancel: any;
}
// 取消重复请求
const pending: Array<PendingType> = [];
const CancelToken = axios.CancelToken;

// 移除重复请求
const removePending = (config: AxiosRequestConfig) => {
    for (const key in pending) {
        const item: number = +key;
        const list: PendingType = pending[key];
        // 当前请求在数组中存在时执行函数体
        if (list.url === config.url && list.method === config.method && JSON.stringify(list.params) === JSON.stringify(config.params) && JSON.stringify(list.data) === JSON.stringify(config.data)) {
            // 执行取消操作
            list.cancel('操作太频繁，请稍后再试');
            // 从数组中移除记录
            pending.splice(item, 1);
        }
    }
};



/* 实例化请求配置 */
const instance = axios.create({
    //统一设置请求头
    headers: {
        // 'X-Requested-With':'XMLHttpRequest',
        'token': StorageSession.get('token') || '',
        // php 的 post 传输请求头一定要这个 不然报错 接收不到值
        // "Content-Type": "application/json;charset=UTF-8",
        // "Access-Control-Allow-Origin-Type": '*'
        /**
         get: {
            'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
            // 在开发中，一般还需要单点登录或者其他功能的通用请求头，可以一并配置进来
        },
         post: {
            'Content-Type': 'application/json;charset=utf-8'
            // 在开发中，一般还需要单点登录或者其他功能的通用请求头，可以一并配置进来
        }
         */
    },
    // 请求时长，请求超时（毫秒）
    timeout: 10000,
    // 请求的base地址 TODO:这块以后根据不同的模块调不同的api
   // baseURL: process.env.VUE_APP_API_URL,
    //     ? "测试"
    //     : "正式",
    // 表示跨域请求时是否需要使用凭证，跨域请求时是否需要使用凭证
    withCredentials: false,
    validateStatus: function () {
        // 使用async-await，处理reject情况较为繁琐，所以全部返回resolve，在业务代码中处理异常
        /**
         * axios 提供了 validateStatus 属性，用于定义对于给定的HTTP 响应状态码是 resolve 或 reject promise。所以，正常设置的情况下，我们会将状态码为 2 系列或者 304 的请求设为 resolve 状态，其余为 reject 状态。结果就是，我们可以在业务代码里，使用 catch 统一捕获响应错误的请求，从而进行统一处理。
         但是，由于我在代码里面使用了 async-await，而众所周知，async-await 捕获 catch 的方式极为麻烦，所以，在此处，我选择将所有响应都设为 resolve 状态，统一在 then 处理。
         */
        return true
    },
    /**
    // 在向服务器发送请求前，序列化请求数据
    transformRequest: [function (data) {
        data = JSON.stringify(data)
        return data
    }],
    // 在传递给 then/catch 前，修改响应数据
    transformResponse: [function (data) {
        if (typeof data === 'string' && data.startsWith('{')) {
            data = JSON.parse(data)
        }
        return data
    }],
     **/
})


/**
 //index.ts中设置这样就不需要base.ts
 const instance = axios.create({
  // 请求的base地址 TODO:这块以后根据不同的模块调不同的api
  baseURL: process.env.VUE_APP_API_URL,
})
 */

/**
 * 请求拦截器
 * 每次请求前，如果存在token则在请求头中携带token
 */
instance.interceptors.request.use(
    config => {

        // removePending(config);
        config.cancelToken = new CancelToken((c) => {
            pending.push({ url: config.url, method: config.method, params: config.params, data: config.data, cancel: c });
        });
        // 登录流程控制中，根据本地是否存在token判断用户的登录情况
        // 但是即使token存在，也有可能token是过期的，所以在每次的请求头中携带token
        // 后台根据携带的token判断用户的登录情况，并返回给我们对应的状态码
        // 而后我们可以在响应拦截器中，根据状态码进行一些统一的操作。

        const token : string = StorageSession.get('token')
        if (token === undefined || token === "") {
            config.headers.Authorization = "";
        }else{
            config.headers.Authorization = token;
        }
        return config;
    },
    error => {
        message.error(error.data.error.message);
        //  '服务器异常，请联系管理员！'
        return Promise.reject(error.data.error.message);
    }

)

// 响应拦截器
instance.interceptors.response.use(function (response) {
       // removePending(response.config);
        const status = response.status
        // 请求成功
        if (status === 200 || status === 204) {
            return Promise.resolve(response);
        } else {
            return Promise.reject(response);
        }
        // 请求失败
    }, function (error) {
        const { response } = error;
        if (response) {
            // 错误状态码转译
            errorHandle(response.status, response.data.message);

            // 超时重新请求
            const config = error.config;
            // 全局的请求次数,请求的间隙
            const [RETRY_COUNT, RETRY_DELAY] = [3, 1000];

            if (config && RETRY_COUNT) {
                // 设置用于跟踪重试计数的变量
                config.__retryCount = config.__retryCount || 0;
                // 检查是否已经把重试的总数用完
                if (config.__retryCount >= RETRY_COUNT) {
                    return Promise.reject(response || { message: error.message });
                }
                // 增加重试计数
                config.__retryCount++;
                // 创造新的Promise来处理指数后退
                const backoff = new Promise<void>((resolve) => {
                    setTimeout(() => {
                        resolve();
                    }, RETRY_DELAY || 1);
                });
                // instance重试请求的Promise
                return backoff.then(() => {
                    return instance(config);
                });
            }

            return Promise.reject(response);
        } else {
            //  '请求超时或服务器异常，请检查网络或联系管理员！'
            // 处理断网的情况
            // eg:请求超时或断网时，更新state的network状态
            // network状态在app.vue中控制着一个全局的断网提示组件的显示隐藏
            // 后续增加断网情况下做的一些操作
            if(error.config && error.config.url){
                message.error("无法连接服务器 ===> "+error.config.url);
            }else if(error.message){
                message.error("无法连接服务器 ===> "+error.message);
            }else{
                message.error("无法连接服务器 ===> "+error);
            }

           //store.commit('networkState', false);
        }
    }
)
// 只需要考虑单一职责，这块只封装axios
export default instance